package org.alwayssuper.web.endpoint;

import org.alwayssuper.netmc.session.Session;
import org.alwayssuper.netmc.session.SessionManager;
import lombok.extern.slf4j.Slf4j;
import org.alwayssuper.commons.model.APIException;
import org.alwayssuper.commons.model.R;
import org.alwayssuper.protocol.basics.GBTMessage;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;

import java.time.Duration;
import java.util.concurrent.TimeoutException;

/**
 * @author yezhihao
 * https://gitee.com/yezhihao/jt808-server
 */
@Slf4j
@Component
public class MessageManager {

    private static final Mono<Void> NEVER = Mono.never();
    private static final Mono OFFLINE_EXCEPTION = Mono.error(new APIException(4000, "离线的客户端（请检查设备是否注册或者鉴权）"));
    private static final Mono OFFLINE_RESULT = Mono.just(R.error("离线的客户端（请检查设备是否注册或者鉴权）").setCode(4000));
    private static final Mono SENDFAIL_RESULT = Mono.just(R.error("消息发送失败").setCode(4001));
    private static final Mono TIMEOUT_RESULT = Mono.just(R.error("消息发送成功,客户端响应超时（至于设备为什么不应答，请联系设备厂商）").setCode(4002));
    private static final Mono RETRY_EXHAUSTED_RESULT = Mono.just(R.error("消息发送失败，已达到最大重试次数").setCode(4003));

    private SessionManager sessionManager;

    public MessageManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    public Mono<Void> notifyR(String sessionId, GBTMessage request) {
        Session session = sessionManager.get(sessionId);
        if (session == null)
            return OFFLINE_EXCEPTION;

        return session.notify(request);
    }

    public Mono<Void> notify(String sessionId, GBTMessage request) {
        Session session = sessionManager.get(sessionId);
        if (session == null)
            return NEVER;

        return session.notify(request);
    }

    public <T> Mono<R<T>> requestR(String sessionId, GBTMessage request, Class<T> responseClass) {
        Session session = sessionManager.get(sessionId);
        if (session == null)
            return OFFLINE_RESULT;

        return session.request(request, responseClass)
                .map(message -> R.success(message))
                .timeout(Duration.ofSeconds(10), TIMEOUT_RESULT)
                .onErrorResume(e -> {
                    log.warn("消息发送失败", e);
                    return SENDFAIL_RESULT;
                });
    }

    public <T> Mono<R<T>> requestR(GBTMessage request, Class<T> responseClass) {
        Session session = sessionManager.get(request.getClientId());
        if (session == null)
            return OFFLINE_RESULT;

        return session.request(request, responseClass)
                .map(message -> R.success(message))
                .timeout(Duration.ofSeconds(10), TIMEOUT_RESULT)
                .onErrorResume(e -> {
                    log.warn("消息发送失败", e);
                    return SENDFAIL_RESULT;
                });
    }

    public <T> Mono<T> request(String sessionId, GBTMessage request, Class<T> responseClass, long timeout) {
        return request(sessionId, request, responseClass).timeout(Duration.ofMillis(timeout));
    }

    public <T> Mono<T> request(String sessionId, GBTMessage request, Class<T> responseClass) {
        Session session = sessionManager.get(sessionId);
        if (session == null)
            return OFFLINE_EXCEPTION;

        return session.request(request, responseClass);
    }


    /**
     * 车载终端控制命令发送（重构版本 - 完全符合国标要求）
     * 1. 接收到车载终端应答指令后完成本次控制传输
     * 2. 规定时间内未收到应答，每间隔1秒重新发送控制命令
     * 3. 重复3次发送控制命令无应答，终止此次控制命令的发送
     * 
     * @param sessionId 会话ID
     * @param controlCommand 控制命令
     * @param responseClass 应答指令类型
     * @return 封装的响应结果
     */
    public <T> Mono<R<T>> sendVehicleControlCommand(String sessionId, GBTMessage controlCommand, Class<T> responseClass) {
        //TODO:会存在mono销毁，但应答正好上传的极限情况，待解决
        return sendVehicleControlCommandWithConfig(sessionId, controlCommand, responseClass, 60, 0, 3);
    }

    /**
     * 车载终端控制命令发送（根据clientId）
     * @param controlCommand 控制命令（包含clientId）
     * @param responseClass 应答指令类型
     * @return 封装的响应结果
     */
    public <T> Mono<R<T>> sendVehicleControlCommand(GBTMessage controlCommand, Class<T> responseClass) {
        String clientId = controlCommand.getClientId();
        if (clientId == null || clientId.isEmpty()) {
            log.warn("车载终端控制命令发送失败：clientId为空");
            return Mono.just((R<T>) R.error("控制命令发送失败：clientId为空").setCode(4000));
        }
        return sendVehicleControlCommand(clientId, controlCommand, responseClass);
    }

    /**
     * 车载终端控制命令发送（可配置参数版本）
     * @param sessionId 会话ID
     * @param controlCommand 控制命令
     * @param responseClass 应答指令类型
     * @param singleTimeoutSec 单次请求超时时间（秒）
     * @param retryIntervalSec 重试间隔时间（秒） 
     * @param maxRetries 最大重试次数
     * @return 封装的响应结果
     */
    public <T> Mono<R<T>> sendVehicleControlCommandWithConfig(String sessionId, GBTMessage controlCommand, Class<T> responseClass, 
                                                              int singleTimeoutSec, int retryIntervalSec, int maxRetries) {
        return Mono.defer(() -> {
            Session session = sessionManager.get(sessionId);
            if (session == null) {
                log.warn("车载终端控制命令发送失败：设备离线 [sessionId={}]", sessionId);
                return Mono.just((R<T>) R.error("离线的客户端（请检查设备是否注册或者鉴权）").setCode(4000));
            }

            log.info("开始发送车载终端控制命令 [sessionId={}, messageId=0x{}, 超时={}秒, 重试间隔={}秒, 最大重试={}次]", 
                    sessionId, Integer.toHexString(controlCommand.getMessageId()).toUpperCase(), 
                    singleTimeoutSec, retryIntervalSec, maxRetries);

            return sendVehicleControlCommandRecursive(session, controlCommand, responseClass, singleTimeoutSec, retryIntervalSec, maxRetries, 0);
        });
    }

    /**
     * 递归重试发送车载终端控制命令（内部方法）
     * @param session 会话对象
     * @param controlCommand 控制命令
     * @param responseClass 响应类型
     * @param timeoutSec 单次超时时间（秒）
     * @param retryIntervalSec 重试间隔（秒）
     * @param maxRetries 最大重试次数
     * @param currentAttempt 当前尝试次数（从0开始）
     * @return 响应结果
     */
    private <T> Mono<R<T>> sendVehicleControlCommandRecursive(Session session, GBTMessage controlCommand, Class<T> responseClass,
                                                             int timeoutSec, int retryIntervalSec, int maxRetries, int currentAttempt) {
        log.debug("发送车载终端控制命令 [sessionId={}, messageId=0x{}, 尝试次数={}/{}]",
                session.getId(), Integer.toHexString(controlCommand.getMessageId()).toUpperCase(), 
                currentAttempt + 1, maxRetries + 1);
        
        return session.requestWithoutTimeout(controlCommand, responseClass)
                .timeout(Duration.ofSeconds(timeoutSec))
                .map(response -> {
                    log.info("车载终端控制命令执行成功，收到应答指令 [sessionId={}, messageId=0x{}, 下发次数={}]",
                            session.getId(), Integer.toHexString(controlCommand.getMessageId()).toUpperCase(), currentAttempt + 1);
                    return R.success(response);
                })
                .onErrorResume(error -> {
                    if (error instanceof TimeoutException) {
                        log.warn("车载终端控制命令响应超时 [sessionId={}, messageId=0x{}, 下发次数={}, 超时={}秒]",
                                session.getId(), Integer.toHexString(controlCommand.getMessageId()).toUpperCase(), 
                                currentAttempt + 1, timeoutSec);
                    } else {
                        log.warn("车载终端控制命令发送失败 [sessionId={}, messageId=0x{}, 下发次数={}, 错误={}]",
                                session.getId(), Integer.toHexString(controlCommand.getMessageId()).toUpperCase(), 
                                currentAttempt + 1, error.getMessage());
                    }
                    
                    if (currentAttempt < maxRetries) {
                        // 还有重试次数，安排下次重试
                        log.info("车载终端控制命令第{}次重试，{}秒后执行 [sessionId={}, messageId=0x{}]",
                                currentAttempt + 1, retryIntervalSec, session.getId(), 
                                Integer.toHexString(controlCommand.getMessageId()).toUpperCase());
                        
                        return Mono.delay(Duration.ofSeconds(retryIntervalSec))
                                .then(sendVehicleControlCommandRecursive(session, controlCommand, responseClass, 
                                                                       timeoutSec, retryIntervalSec, maxRetries, currentAttempt + 1));
                    } else {
                        // 重试次数耗尽
                        log.error("车载终端控制命令发送终止：重复{}次发送无应答 [sessionId={}, messageId=0x{}]", 
                                maxRetries, session.getId(), Integer.toHexString(controlCommand.getMessageId()).toUpperCase());
                        return Mono.just((R<T>) R.error("控制命令发送失败：重复" + maxRetries + "次发送无应答，已终止发送").setCode(4003));
                    }
                });
    }

    /**
     * 发送单次控制命令（内部方法）
     * @param session 会话对象
     * @param controlCommand 控制命令
     * @param responseClass 响应类型
     * @param timeoutSec 超时时间（秒）
     * @param attemptNo 尝试次数（用于日志）
     * @return 响应结果
     */
    private <T> Mono<R<T>> sendSingleControlCommand(Session session, GBTMessage controlCommand, Class<T> responseClass, 
                                                   int timeoutSec, int attemptNo) {
        return session.requestWithoutTimeout(controlCommand, responseClass)
                .timeout(Duration.ofSeconds(timeoutSec))
                .map(response -> {
                    log.info("车载终端控制命令执行成功，收到应答指令 [sessionId={}, messageId=0x{}, 下发次数={}]",
                            session.getId(), Integer.toHexString(controlCommand.getMessageId()).toUpperCase(), attemptNo + 1);
                    return R.success(response);
                })
                .doOnError(error -> {
                    log.debug("单次控制命令发送失败 [sessionId={}, messageId=0x{}, 下发次数={}, 错误={}]",
                            session.getId(), Integer.toHexString(controlCommand.getMessageId()).toUpperCase(), 
                            attemptNo + 1, error.getMessage());
                });
    }

}